/*
 * Copyright Konstantin Triger <kostat@gmail.com> 
 * 
 * This file is part of Jaque - JAva QUEry library <http://code.google.com/p/jaque/>.
 * 
 * Jaque is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jaque is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package jaque.xml;

import jaque.Query;
import jaque.functions.Predicate;
import jaque.misc.*;
import static jaque.Query.*;

import java.util.Iterator;

import org.w3c.dom.*;

public final class DOM {
	private DOM() {
	}

	private static <T extends Node> Iterable<T> filterByName(
			Iterable<T> source, final String name) {
		return where(new Predicate<T>() {
			// @Override
			public Boolean invoke(T t) throws Throwable {
				return t.getNodeName().equals(name);
			}
		}, source);
	}

	private static <T extends Node> Iterable<T> filterByFullName(
			Iterable<T> source, final String namespaceURI,
			final String localName) {
		return where(new Predicate<T>() {
			// @Override
			public Boolean invoke(T t) throws Throwable {
				return t.getLocalName().equals(localName)
						&& (namespaceURI == null || t.getNamespaceURI().equals(
								namespaceURI));
			}
		}, source);
	}

	public static <T extends Attr> Iterable<T> attributes(final T node,
			final String name) {
		return filterByName(attributes(node), name);
	}

	public static <T extends Attr> Iterable<T> attributes(final T node,
			final String namespaceURI, final String localName) {
		return filterByFullName(attributes(node), namespaceURI, localName);
	}

	public static <T extends Attr> Iterable<T> attributes(final T node) {
		final NamedNodeMap attrs = node.getAttributes();
		if (attrs == null)
			return Query.<T> empty();
		return new Iterable<T>() {
			public final Iterator<T> iterator() {

				return new BaseIterator<T>() {
					private int _curIndex;

					@SuppressWarnings("unchecked")
					// @Override
					public final boolean hasNext() {

						if (_curIndex < attrs.getLength())
							return yield((T) attrs.item(_curIndex++));

						return yieldBreak;
					}
				};
			}
		};
	}

	public static Iterable<Node> children(final Node node, final String name) {
		return filterByName(children(node), name);
	}

	public static Iterable<Node> children(final Node node,
			final String namespaceURI, final String localName) {
		return filterByFullName(children(node), namespaceURI, localName);
	}

	public static Iterable<Node> children(final Node node) {
		final Node first = node.getFirstChild();
		if (first == null)
			return Query.<Node> empty();
		return new Iterable<Node>() {
			public final Iterator<Node> iterator() {

				return new BaseIterator<Node>() {

					// @Override
					public final boolean hasNext() {
						Node current = super.next();
						if (current == null)
							return yield(first);

						current = current.getNextSibling();
						if (current != null)
							return yield(current);

						return yieldBreak;
					}
				};
			}
		};
	}

	public static <T extends Element> Iterable<T> descendantsAndSelf(
			final T element, final String name) {
		return filterByName(descendantsAndSelf(element), name);
	}

	public static <T extends Element> Iterable<T> descendantsAndSelf(
			final T element, final String namespaceURI, final String localName) {
		return filterByFullName(descendantsAndSelf(element), namespaceURI,
				localName);
	}

	public static <T extends Element> Iterable<T> descendantsAndSelf(
			final T element) {
		return new Iterable<T>() {
			@SuppressWarnings("unchecked")
			public final Iterator<T> iterator() {

				return new BaseIterator<T>() {
					private int _state;
					private Iterator<Node> _outer;
					private Iterator<T> _inner;

					// @Override
					public final boolean hasNext() {
						switch (_state) {
						case 0:
							_state = 1;
							return yield(element);
						case 1:
							_outer = where(new Predicate<Node>() {
								// @Override
								public Boolean invoke(final Node t)
										throws Throwable {
									return t instanceof Element;
								}
							}, children(element)).iterator();
						case 2:
							if (_outer.hasNext()) {
								_state = 3;
								return yield((T) _outer.next());
							}

							break;

						case 3:
							_inner = descendantsAndSelf(super.next())
									.iterator();
							_state = 4;
						case 4:
							if (_inner.hasNext())
								return yield(_inner.next());

							_state = 2;
							return hasNext();
						}

						return yieldBreak;
					}
				};
			}
		};
	}

	public static <T extends Element> Iterable<T> ancestorsAndSelf(
			final T element, final String name) {
		return filterByName(ancestorsAndSelf(element), name);
	}

	public static <T extends Element> Iterable<T> ancestorsAndSelf(
			final T element, final String namespaceURI, final String localName) {
		return filterByFullName(ancestorsAndSelf(element), namespaceURI,
				localName);
	}

	public static <T extends Element> Iterable<T> ancestorsAndSelf(
			final T element) {
		return new Iterable<T>() {
			public final Iterator<T> iterator() {
				return new BaseIterator<T>() {

					// @Override
					@SuppressWarnings("unchecked")
					public final boolean hasNext() {
						T current = super.next();
						if (current == null)
							return yield(element);

						current = (T) current.getParentNode();
						if (current != null)
							return yield(current);

						return yieldBreak;
					}
				};
			}
		};
	}
}
